﻿#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

//1. 在测试视频(OpenCV安装目录\sources\samples\data)上，使用基于混合高斯模型的背景提取算法，提取前景并显示(显示二值化图像，前景为白色)。
//2. 在1基础上，将前景目标进行分割，进一步使用不同颜色矩形框标记，并在命令行窗口中输出每个矩形框的位置和大小。


//题2中如何做到圈住某人的矩形框颜色保持不变？这里用了每帧随机颜色
//labelTargets(image, fgMask, 5);//当thresh取100时，视频中远处出现的人没被选中
//可以看出随着播放时间越长，背景里头的人的影子占比越来越少了. 人物脚下的影子噪点很多.

int labelTargets(Mat& src, Mat& mask, int thresh = 100);

RNG rng(time(0));

int main()
{
	const char* fn = ".\\768x576.avi";
	VideoCapture cap;
	Mat source, image, foreGround, backGround, fgMask;
	Ptr<BackgroundSubtractor> pBgModel = createBackgroundSubtractorMOG2().dynamicCast<BackgroundSubtractor>();//基于混合高斯模型的背景提取算法

	

	cap.open(fn);
	if (!cap.isOpened())
		cout << "无法打开视频文件： " << fn << endl;


	for (;;)
	{
		cap >> source;
		if (source.empty())
			break;

		resize(source, image, Size(source.cols / 2, source.rows / 2), INTER_LINEAR);//因为要对每个像素都建模，图像太大的话，不利于建模。
		//source.copyTo(image);

		if (foreGround.empty())
		{
			foreGround.create(image.size(), image.type());
			//分开窗口
			Point start_pos(50, 100);
			namedWindow("Source", 0);
			resizeWindow("Source", image.size().width, image.size().height);
			moveWindow("Source", start_pos.x, start_pos.y);

			namedWindow("Background", 0);
			resizeWindow("Background", image.size().width, image.size().height);
			moveWindow("Background", start_pos.x + image.size().width, start_pos.y);

			namedWindow("Foreground", 0);
			resizeWindow("Foreground", image.size().width, image.size().height);
			moveWindow("Foreground", start_pos.x, start_pos.y + image.size().height);

			namedWindow("Foreground Mask", 0);
			resizeWindow("Foreground Mask", image.size().width, image.size().height);
			moveWindow("Foreground Mask", start_pos.x + image.size().width, start_pos.y + image.size().height);
		}

		pBgModel->apply(image, fgMask);

		//打印fgMask
		//cout << fgMask.type() << " " << image.type();
		//for (int row_idx = 0; row_idx < 5; row_idx++)
		//{
		//	uchar* p = image.ptr<uchar>(row_idx);

		//	for (int col_idx = 0; col_idx < image.cols; col_idx++)
		//	{
		//		cout << (int)p[col_idx] << ";";
		//	}

		//	cout << endl;
		//}


		//GaussianBlur(fgMask, fgMask, Size(5, 5), 0);
		threshold(fgMask, fgMask, 30, 255, THRESH_BINARY);//超过30部分取255(极亮)， 否则取0

		foreGround = Scalar::all(0);
		image.copyTo(foreGround, fgMask);
		// 标记找到的运动目标
		int nTargets = labelTargets(image, fgMask, 5);
		cout << "共检测到 " << nTargets << " 个目标" << endl;

		pBgModel->getBackgroundImage(backGround);

		// 显示原始图像及背景，前景
		imshow("Source", image);
		imshow("Background", backGround); 
		imshow("Foreground", foreGround);
		imshow("Foreground Mask", fgMask);



		// 以下检测是否终止(按下ESC终止，对应ASCII 27)
		char key = waitKey(100); // 每一帧等待100ms
		if (key == 27)
			break;
	}

	waitKey(0);
}

int labelTargets(Mat& src, Mat& mask, int thresh)
{
	// 以下是图像分割
	Mat seg = mask.clone();
	vector<vector<Point> > cnts;
	findContours(seg, cnts, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);


	// 以下进行筛选
	float area;
	Rect rect;
	int count = 0;
	string strCount;
	for (int i = cnts.size() - 1; i >= 0; i--)
	{
		vector<Point> c = cnts[i];
		area = contourArea(c);
		if (area < thresh)
			continue;

		count++;

		rect = boundingRect(c);
		// 在原始图像上画出包围矩形，并给每个矩形标号
		rectangle(src, rect, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 1);
		cout << "矩形框id " << i << "  面积: " << area << " 坐标:" << rect.x << "," << rect.y << endl;

		stringstream ss;
		ss << count;
		ss >> strCount;
		putText(src, strCount, Point(rect.x, rect.y), CV_FONT_HERSHEY_PLAIN, 0.5, Scalar(0, 0xff, 0));
	}

	return count;
}
